package com.xucia.jsponic.data;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.ScriptableObject;

import com.xucia.jsponic.JsponicRuntime;


public class DataObjectCloner {
	Persistable container;
	Map copied = new HashMap();
	public DataObjectCloner(Persistable original) {
		container = original;
	}
	public static boolean isDescendant(Persistable possibleParent, Object child) {
		if (child instanceof Persistable) {
			Object parent = ((Persistable) child).get(GlobalData.PARENT_FIELD);
			 if (parent instanceof Persistable) {
				if (parent == possibleParent)
					return true;
				else
					return isDescendant(possibleParent,(Persistable) parent);
			 }
		}
		 return false;
	}
	public Persistable clone() {
		return innerClone(container);
	}
	public Persistable innerClone(Persistable original) {
		if (copied.get(original) != null)
			return (Persistable) copied.get(original);		
		PersistableImpl copy;
		try {
			copy = (PersistableImpl) (original.getClass() == NativeArray.class ? JsponicRuntime.newArray() : original.getClass().newInstance());
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
		copied.put(original,copy);
    	for (String key : original.keySet()) {
    		Object value = original.get(key);
    		if (!GlobalData.HISTORY_FIELD.equals(key) && !GlobalData.VERSION_NUMBER_FIELD.equals(key)) {
	    		if (container == value || isDescendant(container, value))
	    			value = innerClone((Persistable) value);
	    		if (value == null) 
	    			throw new RuntimeException("Not supported yet");
	    		if (value == container)
	    			throw new RuntimeException("Should not reference container");
	    		copy.set(key, value);
    		}
    	}
		Object basis = original.getPrototype();
		if (basis != null && basis != ScriptableObject.getClassPrototype(GlobalData.getGlobalScope(),"Array") && 
				basis != ScriptableObject.getClassPrototype(GlobalData.getGlobalScope(),"Object") && 
				!(original instanceof Function)) {
			copy.setPrototype(innerClone((Persistable) basis));
		}
		
    	if (original instanceof List) { //TODO: It can actually be a contains repeating even if there is nothing in the list, that should be reflected here
    		for (Object value : (List) original) {
        		if (isDescendant(container, value))
        			value = innerClone( (Persistable) value);
        		((List) copy).add(value);
    		}
		}
    	return copy;
	}
}
